package com.imooc.controller;

import com.imooc.pojo.Users;
import com.imooc.pojo.vo.UsersVO;
import com.imooc.service.UserService;
import com.imooc.utils.IMOOCJSONResult;
import com.imooc.utils.JsonUtils;
import com.imooc.utils.MD5Utils;
import com.imooc.utils.RedisOperator;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import springfox.documentation.annotations.ApiIgnore;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Arrays;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicReference;


/**
 * @Author: 顾志杰
 * @Date: 2019/11/13
 * @Time: 9:33
 */


@Controller
@ApiIgnore
public class SSOController {
    @Autowired
    private UserService userService;
    @Autowired
    private RedisOperator redisOperator;

    final static Logger logger = LoggerFactory.getLogger(SSOController.class);

    public static final String REDIS_USER_TOKEN = "redis_user_token";

    public static final String REDIS_USER_TICKET = "redis_user_ticket";

    public static final String REDIS_TMP_TICKET = "redis_tmp_ticket";

    public static final String COOKIE_USER_TICKET = "cookie_user_ticket";

    @GetMapping("/login")
    public Object login(String returnUrl
            , Model model
            , HttpServletRequest request
            , HttpServletResponse response) {
        logger.info("info: returnUrl~");
        model.addAttribute("returnUrl", returnUrl);
        //1.获取userTicket门票，如果cookie中能够获取到，证明用户登陆过，此时签发一个一次性的临时票据并且回跳
        String userTicket = getCookie(request, COOKIE_USER_TICKET);
        boolean isVerified = verifyUserTicket(userTicket);
        if (isVerified) {
            String tmpTicket = createTmpTicket();
            return "redirect:" + returnUrl + "?tmpTicket=" + tmpTicket;
        }

        //2.用户从未登陆过，第一次进入则跳转到CAS的统一登录页面
        return "login";
    }

    /**
     * 效验cas全局用户门票
     *
     * @return
     */
    private boolean verifyUserTicket(String userTicket) {
        // 1.验证cas门票不能为null
        if (StringUtils.isBlank(userTicket)) {
            return false;
        }
        //2.验证cas门票是否有效
        String userId = redisOperator.get(REDIS_USER_TICKET + ":" + userTicket);
        if (StringUtils.isBlank(userId)) {
            return false;
        }
        // 3.验证门票对应的user会话是否存在
        String userRedis = redisOperator.get(REDIS_USER_TOKEN + ":" + userId);
        if (StringUtils.isBlank(userRedis)) {
            return false;
        }
        return true;
    }

    /**
     * CAS的统一登录接口
     * 目的：
     * 1.登录后创建用户的全局会话                     ->   uniqueToken
     * 2.创建用户的全局门票，用以标识在CAS端是否登录  ->   userTicket
     * 3.创建用户的临时票据，用于回跳回传             ->   tmpTicket
     *
     * @param username
     * @param password
     * @param returnUrl
     * @param model
     * @param request
     * @param response
     * @return
     * @throws Exception
     */
    @PostMapping("/doLogin")
    public Object doLogin(String username
            , String password
            , String returnUrl
            , Model model
            , HttpServletRequest request
            , HttpServletResponse response) throws Exception {
        model.addAttribute("returnUrl", returnUrl);
        if (StringUtils.isBlank(username) || StringUtils.isBlank(password)) {
            model.addAttribute("errmsg", "用户名或密码不能为空");
            return "login";
        }
        // 1.实现登录
        Users users = userService.queryUserForLogin(username, MD5Utils.getMD5Str(password));
        if (!Optional.ofNullable(users).isPresent()) {
            model.addAttribute("errmsg", "用户名密码不正确");
            return "login";
        }
        // 2.实现用户的redis会话
        String uniqueToken = UUID.randomUUID().toString().trim();
        UsersVO usersVO = new UsersVO();
        BeanUtils.copyProperties(users, usersVO);
        usersVO.setUserUniqueToken(uniqueToken);
        redisOperator.set(REDIS_USER_TOKEN + ":" + users.getId(), JsonUtils.objectToJson(usersVO));

        // 3.生成ticket门票，全局门票，代表用户在CAS端登陆过
        String userTicket = UUID.randomUUID().toString().trim();

        // 3.1 用户全局门票需要放入CAS端的cookie中
        setCookie(COOKIE_USER_TICKET, userTicket, response);

        // 4.userTicket关联用户id，并且放入到redis中，代表这个用户有门表了，可以在各个服务端登录
        redisOperator.set(REDIS_USER_TICKET + ":" + userTicket, users.getId());

        // 5.生成临时票据，回跳到调用端网站，是由CAS端所签发的一个一次性临时ticket
        String tmpTicket = createTmpTicket();
        /**
         * userTicket: 用于标识用户在CAS端的一个登录状态：已经登录
         * tmpTicket: 用于颁发给用户进行一次性的验证的票据，有时效性
         */

        /**
         * 举例：
         *      我们去动物园玩耍，大门口买了一张统一的门票，这个就是CAS系统的全局门票和用户全局会话。
         *      动物园里有一些小的景点，需要凭你的门票去领取一次性的票据，有了这张票据以后就能去一些小的景点游玩了。
         *      这样的一个个的小景点其实就是我们这里所对应的一个个的站点。
         *      当我们使用完毕这张临时票据以后，就需要销毁。
         */
//        return "login";
        return "redirect:" + returnUrl + "?tmpTicket=" + tmpTicket;

    }

    /**
     * 验证临时票据
     *
     * @param tmpTicket
     * @param request
     * @param response
     * @return
     * @throws Exception
     */
    @PostMapping("/verifyTmpTicket")
    @ResponseBody
    public IMOOCJSONResult verifyTmpTicket(String tmpTicket
            , HttpServletRequest request
            , HttpServletResponse response) throws Exception {
//        response.setHeader("Access-Control-Allow-Origin", "http://localhost:8080");

        // 使用一次性临时票据来验证用户是否登录，如果登录过，把用户会话信息返回给站点
        // 使用完毕后，需要销毁临时票据
        String tmpTicketValue = redisOperator.get(REDIS_TMP_TICKET + ":" + tmpTicket);
        if (StringUtils.isBlank(tmpTicketValue)) {
            return IMOOCJSONResult.errorMsg("用户临时票据异常");
        }
        // 0.如果临时票据ok则需要销毁，并且拿到CAS端的cookie中的全局userTicket，以此在获取用户会话
        if (tmpTicketValue.equals(MD5Utils.getMD5Str(tmpTicketValue))) {
            return IMOOCJSONResult.errorMsg("用户临时票据异常");
        } else {
            // 销毁临时票据
            redisOperator.del(REDIS_TMP_TICKET + ":" + tmpTicket);
        }
        // 1.验证并且获取用户的userTicket
        String userTicket = getCookie(request, COOKIE_USER_TICKET);
        String userId = redisOperator.get(REDIS_USER_TICKET + ":" + userTicket);
        if (StringUtils.isBlank(userId)) {
            return IMOOCJSONResult.errorMsg("用户临时票据异常");
        }
        // 2.验证门票对应的user会话是否存在
        String userRedis = redisOperator.get(REDIS_USER_TOKEN + ":" + userId);
        if (StringUtils.isBlank(userRedis)) {
            return IMOOCJSONResult.errorMsg("用户临时票据异常");
        }
        //验证成功，返回ok ，携带用户会话
        return IMOOCJSONResult.ok(JsonUtils.jsonToPojo(userRedis, UsersVO.class));
    }
    @PostMapping("/logout")
    @ResponseBody
    public IMOOCJSONResult logout(String userId
            , HttpServletRequest request
            , HttpServletResponse response) {
        //1.获取cas中的用户门票
        String userTicket = getCookie(request, COOKIE_USER_TICKET);
        //2.清除userTicket票据，redis、cookies
        deleteCookie(COOKIE_USER_TICKET,response);
        redisOperator.del(REDIS_USER_TICKET + ":" + userTicket);
        //3.清除用户全局会话（分布式会话）;
        redisOperator.del(REDIS_USER_TOKEN + ":" + userId);
        return IMOOCJSONResult.ok();
    }

    /**
     * 创建临时票据
     *
     * @return
     */
    private String createTmpTicket() {
        String tmpTicket = UUID.randomUUID().toString().trim();
        try {
            redisOperator.set(REDIS_TMP_TICKET + ":" + tmpTicket, MD5Utils.getMD5Str(tmpTicket), 600);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return tmpTicket;
    }


    private void setCookie(String key, String val, HttpServletResponse response) {
        Cookie cookie = new Cookie(key, val);
        cookie.setDomain("localhost");
        cookie.setPath("/");
        response.addCookie(cookie);
    }


    private String getCookie(HttpServletRequest request, String key) {
        Cookie[] cookieList = request.getCookies();
        if (cookieList == null || StringUtils.isBlank(key)) {
            return null;
        }
        AtomicReference<String> cookieValue = new AtomicReference<>();
        Arrays.stream(cookieList).filter(cookie -> cookie.getName().equals(key)).forEach(cookie -> cookieValue.set(cookie.getValue()));
        return cookieValue.toString();
    }

    private void deleteCookie(String key, HttpServletResponse response) {
        Cookie cookie = new Cookie(key, null);
        cookie.setDomain("localhost");
        cookie.setPath("/");
        cookie.setMaxAge(-1);
        response.addCookie(cookie);
    }
}
